/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.rultax.neural.genetic;

import com.rultax.exception.NeuralNetworkError;
import java.util.HashSet;
import java.util.Set;

/**
 *
 * @author Scott
 */
public abstract class Chromosome<GENE_TYPE, GA_TYPE extends GeneticAlgorithm<?>> 
        implements Comparable<Chromosome<GENE_TYPE, GA_TYPE>> {
    
    private double cost;
    private GENE_TYPE[] genes;
    private GA_TYPE geneticAlgorithm;
    
    public abstract void calculateCost() throws NeuralNetworkError;
    
    @Override
    public int compareTo(final Chromosome<GENE_TYPE, GA_TYPE> other){
        if(getCost() > other.getCost())
            return 1;
        else
            return -1;
    }
    
    public double getCost(){
        return cost;
    }
    
    public GENE_TYPE getGene(final int gene){
        return genes[gene];
    }
    
    public GENE_TYPE[] getGenes(){
        return genes;
    }
    
    public GA_TYPE getGeneticAlgorithm(){
        return geneticAlgorithm;
    }
    
    private GENE_TYPE getNotTaken(final Chromosome<GENE_TYPE, GA_TYPE> source,
            final Set<GENE_TYPE> taken){
        
        final int geneLength = source.size();
        
        for (int i = 0; i < geneLength; i++) {
            final GENE_TYPE trial = source.getGene(i);
            if(!taken.contains(trial)){
                taken.add(trial);
                return trial;
            }
        }
        return null;
    }
    
    public void mate(final Chromosome<GENE_TYPE, GA_TYPE> father,
            final Chromosome<GENE_TYPE, GA_TYPE> offspring1,
            final Chromosome<GENE_TYPE, GA_TYPE> offspring2)
            throws NeuralNetworkError{
        
        final int geneLength = getGenes().length;
        final int cutpoint1 = (int)(Math.random() * (geneLength - getGeneticAlgorithm().getCutLength()));
        final int cutpoint2 = cutpoint1 + getGeneticAlgorithm().getCutLength();
        
        final Set<GENE_TYPE> taken1 = new HashSet<GENE_TYPE>();
        final Set<GENE_TYPE> taken2 = new HashSet<GENE_TYPE>();
        
        for (int i = 0; i < geneLength; i++) {
            if((i < cutpoint1) || (i > cutpoint2)){}
            else{
                offspring1.setGene(i, father.getGene(i));
                offspring2.setGene(i, getGene(i));
                taken1.add(offspring1.getGene(i));
                taken2.add(offspring2.getGene(i));
            }
        }
        
        for (int i = 0; i < geneLength; i++) {
            if((i < cutpoint1) || i > cutpoint2){
                if(getGeneticAlgorithm().isPreventRepeat()){
                    offspring1.setGene(i, getNotTaken(this, taken1));
                    offspring2.setGene(i, getNotTaken(father, taken2));
                }else{
                    offspring1.setGene(i, this.getGene(i));
                    offspring2.setGene(i, father.getGene(i));
                }
            }
        }
        
        if(Math.random() < geneticAlgorithm.getMutationPercent())
            offspring1.mutate();
        
        if(Math.random() < geneticAlgorithm.getMutationPercent())
            offspring2.mutate();
        
        offspring1.calculateCost();
        offspring2.calculateCost();
    }
    
    public abstract void mutate();
    
    public void setCost(final double cost){
        this.cost = cost;
    }
    
    public void setGene(final int gene, final GENE_TYPE value){
        this.genes[gene] = value;
    }
    
    public void setGenes(final GENE_TYPE[] genes) throws NeuralNetworkError{
        this.genes = genes;
    }
    
    public final void setGenesDirect(final GENE_TYPE[] genes) throws NeuralNetworkError{
        this.genes = genes;
    }
    
    public void setGeneticAlgorithm(final GA_TYPE geneticAlgorithm){
        this.geneticAlgorithm = geneticAlgorithm;
    }
    
    private int size(){
        return genes.length;
    }
    
    @Override
    public String toString(){
        final StringBuilder builder = new StringBuilder();
        builder.append("[Chromosome: cost=");
        builder.append(getCost());
        builder.append("]");
        return builder.toString();
    }
}
